/**
 * @brief 一个简单的协议报撤单功能演示
 *
 * 演示功能：
 * 1. 通过配置文件创建API实例；
 * 2. 启动API，启动成功后，调用login接口登录柜台；
 * 3. 等数据加载完毕后，使用协议方式发送一手多头开仓报单请求；
 * 4. 等待3秒后，针对所有的报单，使用协议方式尝试撤单；
 * 5. 等待3秒后，调用logout接口登出柜台；
 */

#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>

#include <map>
#include "ExampleTrader.h"

#pragma pack(push, 1)

/// 结构体说明:      FAIR格式 报单消息完整结构体(包括头和域)
/// 对齐说明      : 字段1字节对齐，结构体64字节对齐，总共64字节;
/// 注意事项      : 
/// 1. Token字段为4字节;
/// 2. InstrumentIndex字段4字节, 报单时需要填写;
/// 3. 结构体64字节对齐
struct CXeleFairInputOrderMsg {
    ///------FAIR消息头------
    uint8_t                 messageId;                      ///< 消息id, 报单操作为101
    uint8_t                 clientIndex;                    ///< 客户端index
    uint32_t                token;                          ///< 客户端token
    int                     seqNo;                          ///< 消息序列号
    uint32_t                requestID;                      ///< 请求id (建议单调递增, 有效范围为(0~0xfffffffe)
    ///------FAIR消息域------
    int                     orderLocalNo;                   ///< 报单本地id
    double                  limitPrice;                     ///< 报单价格
    char                    instrumentID[16];               ///< 合约代码
    short                   volumeTotalOriginal;            ///< 数量
    char                    insertType;                     ///< 输入报单类型 (具体值类型请参考文档说明)
    short                   minVolume;                      ///< 最小成交数量
    uint8_t                 exchangeFront;                  ///< 前置信息(不指定前置填写为0, 指定前置需要加上偏移量10, 如指定前置3，则需要填写13)
    uint32_t                instrumentIndex;                ///< 合约序号
    char                    reserve[12];                    ///< 预留字段
};

/// 结构体说明:      FAIR格式 撤单消息完整结构体(包括头和域)
/// 对齐说明      : 字段1字节对齐，结构体64字节对齐， 总共64字节;
/// 注意事项      : 
/// 1. Token字段为4字节;
struct CXeleFairOrderActionMsg{
    ///------FAIR消息头------
    uint8_t                 messageId;                      ///< 消息id, 撤单操作为103
    uint8_t                 clientIndex;                    ///< 客户端index
    uint32_t                token;                          ///< 客户端token
    int                     seqNo;                          ///< 消息序列号
    uint32_t                requestID;                      ///< 请求id(建议单调递增, 有效范围为(0~0xfffffffe)
    ///------FAIR消息域------
    int                     actionLocalNo;                  ///< 本地操作编号
    int                     orderSysNo;                     ///< 被撤单柜台编码
    char                    actionFlag;                     ///< 报单操作标志
    int                     orderLocalNo;                   ///< 本地报单编号(暂未使用)
    char                    reserve[37];                    ///< 预留字段
};

#pragma pack(pop)


class Example_03_Trader : public ExampleTrader {
public:
    Example_03_Trader() = default;

    ~Example_03_Trader() override {
        // release api.
        if (mApi) {
            mApi->stop();
            delete mApi;
            mApi = nullptr;
        }
    };

    void onStart(int errorCode, bool isFirstTime) override {
        ExampleTrader::onStart(errorCode, isFirstTime);

        printf("api start ok.\n");
    }

    void onStop(int errorCode) override {
        ExampleTrader::onStop(errorCode);

        printf("api stop ok.\n");
    }

    void onServerReboot() override {
        printf("trade system reboot, clear local data.\n");
        mInstrument = nullptr;
        mOrders.clear();
    }

    void onLogin(int errorCode, int exchangeCount) override {
        ExampleTrader::onLogin(errorCode, exchangeCount);

        if (errorCode != 0) {
            printf("login failed, error code: %d.\n", errorCode);
        } else {
            printf("login success, exchange count=%d.\n", exchangeCount);
        }
    }

    void onLogout(int errorCode) override {
        ExampleTrader::onLogout(errorCode);

        printf("logout success.\n");
    }

    void onReadyForTrading(const XTFAccount *account) override {
        ExampleTrader::onReadyForTrading(account);

        printf("ready for trading.\n");
        mInstrument = mApi->getInstrumentByID(mInstrumentID.c_str()); // 查询可用的合约对象
        if (!mInstrument) {
            printf("instrument not found: %s\n", mInstrumentID.c_str());
            exit(0);
        }

        mExchange = mInstrument->getProduct()->getProductGroup()->getExchange();
        if (!mExchange) {
            printf("instrument error, exchange invalid: %s\n", mInstrumentID.c_str());
            exit(0);
        }

        mLocalOrderID = account->lastLocalOrderID;   // API&协议报单使用
        mLocalActionID = account->lastLocalActionID; // 协议报单使用
    }

    void onLoadFinished(const XTFAccount *account) override {
        ExampleTrader::onLoadFinished(account);

        printf("load data finished.\n");
    }

    void onAccount(int event, int action, const XTFAccount *account) override {
        printf("account is changed.\n");
    }

    void onExchange(int event, int channelID, const XTFExchange *exchange) override {
        printf("exchange is changed.\n");
    }

    void onInstrument(int event, const XTFInstrument *instrument) override {
        printf("instrument is changed.\n");
    }

    void onChangePassword(int errorCode) override {
        printf("password is changed.\n");
    }

    void onOrder(int errorCode, const XTFOrder *order) override {
        printf("recv order report: action=%d, sys-id=%d, "
               "status=%s, error-code=%d.\n",
               order->actionType, order->sysOrderID,
               getOrderStatus(order->orderStatus), errorCode);
        if (errorCode == 0) {
            if (order->orderStatus == XTF_OS_Queuing) {
                mOrders[order->sysOrderID] = order;
            }
        }
    }

    void onCancelOrder(int errorCode, const XTFOrder *cancelOrder) override {
        printf("recv cancel order report: sys-id=%d, status=%s, error-code=%d.\n",
               cancelOrder->sysOrderID, getOrderStatus(cancelOrder->orderStatus), errorCode);
        if (errorCode == 0 || errorCode == 1198) {
            auto iter = mOrders.find(cancelOrder->sysOrderID);
            if (iter != mOrders.end())
                mOrders.erase(iter);
        }
    }

    void onTrade(const XTFTrade *trade) override {
        printf("recv trade report: trade-id=%ld, price=%.4f, volume=%d/%d, sys-order-id=%d\n",
                trade->tradeID, trade->tradePrice, trade->order->totalTradedVolume,
                trade->order->orderVolume, trade->order->sysOrderID);
    }

    void onEvent(const XTFEvent &event) override {
        printf("recv event.\n");
    }

    void onError(int errorCode, void *data, size_t size) override {
        printf("recv error.\n");
    }

    void start() {
        if (mApi) {
            printf("error: trader has been started.\n");
            return;
        }

        mLocalOrderID = 0;
        mApi = makeXTFApi(mConfigPath.c_str());
        if (mApi == nullptr) {
            printf("error: create xtf api failed, please check config: %s.\n", mConfigPath.c_str());
            exit(0);
        }

        // 保存报单的地址和端口，用于协议方式发送报撤单。
        mTradeServerIp = mApi->getConfig("TRADE_SERVER_IP");
        mTradeServerPort = mApi->getConfig("TRADE_SERVER_PORT");
        openUdpSocket();

        printf("api starting..., config: %s.\n", mConfigPath.c_str());
        int ret = mApi->start(this);
        if (ret != 0) {
            printf("start failed, error code: %d\n", ret);
            exit(0);
        }
    }

    void stop() {
        if (!mApi) {
            printf("error: trader is not started.\n");
            return;
        }

        closeUdpSocket();

        printf("api stopping...\n");
        int ret = mApi->stop();
        if (ret == 0) {
            delete mApi;
            mApi = nullptr;
        } else {
            printf("api stop failed, error code: %d\n", ret);
        }
    }

    void login() {
        if (!mApi) return;
        printf("api logging in...\n");
        int ret = mApi->login();
        if (ret != 0) {
            printf("api logging in failed, error code: %d\n", ret);
        }
    }

    void logout() {
        if (!mApi) return;
        printf("api logging out...\n");
        int ret = mApi->logout();
        if (ret != 0) {
            printf("api logging out failed, error code: %d\n", ret);
        }
    }
    // API接口方式报单
    void insertOrder() {
        if (!mApi) {
            printf("api is not started.\n");
            return;
        }

        if (!mInstrument) {
            printf("instrument is not found: %s\n", mInstrumentID.c_str());
            return;
        }

        printf("api prepare order...\n");
        XTFInputOrder order{};
        order.localOrderID = 1; // 建议使用本地唯一的编号
        order.direction = XTF_D_Buy;
        order.offsetFlag = XTF_OF_Open;
        order.orderType = XTF_ODT_Limit;
        order.price = mPrice;
        order.volume = mVolume;
        order.channelSelectionType = XTF_CS_Auto;
        order.channelID = 0;
        order.orderFlag = XTF_ODF_Normal;
        order.instrument = mInstrument;

        printf("api insert order...\n");
        int ret = mApi->insertOrder(order);
        if (ret != 0) {
            printf("api insert order failed, error code: %d\n", ret);
        }
    }
    // API接口方式撤单
    void cancelOrder() {
        if (!mApi) {
            printf("api is not started.\n");
            return;
        }

        if (mOrders.empty()) {
            printf("no orders need cancel.\n");
            return;
        }

        printf("api cancel order...\n");
        for (auto &iter: mOrders) {
            printf("cancel order: sys-id=%d.\n", iter.first);
            mApi->cancelOrder(iter.second);
            usleep(500000); // sleep 500ms;
        }
    }

    // 协议方式报单
    void insertOrderByUdp() {
        if (!mApi) {
            printf("api is not started.\n");
            return;
        }

        if (!mInstrument) {
            printf("instrument is not found: %s\n", mInstrumentID.c_str());
            return;
        }

        if (!mExchange) {
            printf("exchange invalid\n");
            return;
        }

        printf("udp prepare order...\n");
        CXeleFairInputOrderMsg order{};
        order.messageId = 0x65;
        order.clientIndex = mExchange->clientIndex;
        order.token = mExchange->clientToken;
        order.orderLocalNo = ++mLocalOrderID;
        order.requestID = ++mRequestID;
        order.seqNo = ++mSeqNo;
        order.limitPrice = mPrice;
        strcpy(order.instrumentID, "au2212");
        order.instrumentIndex = mInstrument->instrumentIndex;
        order.volumeTotalOriginal = mVolume;
        order.minVolume = 1;
        order.insertType = 1;
        order.exchangeFront = 0;

        printf("udp insert order...\n");
        sendUdpData(&order, sizeof(order));
    }

    // 协议方式撤单
    void cancelOrderByUdp() {
        if (!mApi) {
            printf("api is not started.\n");
            return;
        }

        if (mOrders.empty()) {
            printf("no orders need cancel.\n");
            return;
        }

        if (!mExchange) {
            printf("exchange invalid\n");
            return;
        }

        printf("udp cancel order...\n");
        for (auto &iter: mOrders) {
            printf("cancel order: sys-id=%d.\n", iter.first);
            CXeleFairOrderActionMsg orderAction{};
            orderAction.messageId = 0x67;
            orderAction.clientIndex = mExchange->clientIndex;
            orderAction.token = mExchange->clientToken;
            orderAction.seqNo = ++mSeqNo;
            orderAction.requestID = ++mRequestID;
            orderAction.actionLocalNo = ++mLocalActionID;
            orderAction.orderSysNo = iter.first;
            orderAction.actionFlag = '0';
            orderAction.orderLocalNo = iter.second->localOrderID;
            sendUdpData(&orderAction, sizeof(orderAction));
            usleep(500000); // sleep 500ms;
        }
    }
    
    /// 协议创建udp socket
    void openUdpSocket() {
        if (mUdpSocket != -1) return;
        mUdpSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (mUdpSocket == -1) {
            printf("udp create socket failed: %d\n", errno);
            exit(1);
        }
        printf("udp channel create socket success: %d\n", mUdpSocket);

        std::string remoteIp = mTradeServerIp;
        uint16_t remotePort = (uint16_t) std::stoi(mTradeServerPort);
        struct sockaddr_in sa{};
        memset(&sa, 0, sizeof(sa));
        sa.sin_family = AF_INET;
        sa.sin_addr.s_addr = inet_addr(remoteIp.c_str());
        sa.sin_port = htons(remotePort);

        int result = connect(mUdpSocket, (struct sockaddr *) &sa, sizeof(sockaddr_in));
        if (result == -1) {
            printf("udp channel connect[%s:%u] failed, errno: %d.", remoteIp.c_str(), remotePort, errno);
            ::close(mUdpSocket); // 释放创建的套接字
            mUdpSocket = -1;
            exit(2);
        }
    }
    
    /// 协议关闭udp socket
    void closeUdpSocket() {
        if (mUdpSocket == -1) return;
        ::close(mUdpSocket);
        mUdpSocket = -1;
    }
    
    /// 协议发送udp数据
    void sendUdpData(const void *data, size_t len) {
        int result = ::send(mUdpSocket, data, len, 0);
        if (result == (int) len) {
            printf("udp send ok, data size: %d\n", result);
        } else {
            printf("udp send error: %d\n", result);
        }
    }

private:
    const XTFExchange *mExchange;
    const XTFInstrument *mInstrument;
    std::map<int, const XTFOrder *> mOrders;
    int mLocalOrderID = 0;
    int mLocalActionID = 0;
    int mStatus = 0;
    int mUdpSocket = -1;
    std::string mTradeServerIp;
    std::string mTradeServerPort;
    int mSeqNo = 0;
    uint32_t mRequestID = 0;
};


/**
 * @brief 一个简单的协议报撤单功能演示
 *
 * API登录柜台后，使用协议方式报一手多头开仓单，等待3秒后，使用协议方式尝试撤单。
 *
 * @param configPath
 * @param instrumentId
 * @param price
 * @param volume
 */
void runExample(const std::string &configPath, const std::string &instrumentId, double price, int volume) {
    printf("start example 03.\n");

    Example_03_Trader trader;
    trader.setConfigPath(configPath);
    trader.setInstrumentID(instrumentId);
    trader.setPrice(price);
    trader.setVolume(volume);

    trader.start();
    while (!trader.isStarted())
        trader.wait(1, "wait for trader started");

    trader.login();
    while (!trader.isLoadFinished())
        trader.wait(1, "wait for data load finished");

    trader.insertOrderByUdp();
    trader.wait(3, "wait for order inserted");

    trader.cancelOrderByUdp();
    trader.wait(3, "wait for order canceled");

    trader.logout();
    while (!trader.isLoggedOut())
        trader.wait(1, "wait for trader logout");

    trader.stop();
    while (!trader.isStopped())
        trader.wait(1, "wait for trader stopped");
}

int main(int argc, const char *argv[]) {
    printf("api version: %s.\n", getXTFVersion());

    // TODO: 解析传入参数，提取相关的配置
    std::string configPath = "../config/xtf_trader_api.config";
    std::string instrumentId = "au2212";
    double price = 301.50f;
    int volume = 1;
    runExample(configPath, instrumentId, price, volume);
    return 0;
}
